#include <getopt.h>

#include "../ar8030_usb_test_tool/ar8030_com.h"
#include "CRC.h"
#include "bb_api.h"
#include "chk.hpp"
#include <fstream>
#include <inttypes.h>
#include <iostream>
#include <stdio.h>
#include <string.h>

const int max_size = 0x300;

int ar8030_upgrade_write(bb_dev_handle_t* pdev, unsigned long long addr, unsigned short len, unsigned char* data)
{
    bb_set_hot_upgrade_write_in_t  in  = {};
    bb_set_hot_upgrade_write_out_t out = {};

    in.seq  = 5673;
    in.addr = (unsigned int)addr;
    in.len  = len;
    memcpy(in.data, data, len);
    int ret = bb_ioctl(pdev, BB_SET_HOT_UPGRADE_WRITE, &in, &out);
    if (ret || out.ret != 0) {
        printf("BB_SET_HOT_UPGRADE_WRITE err!\n");
        return -1;
    }

    return 0;
}

int ar8030_upgrade_chk_crc(bb_dev_handle_t* pdev, uint32_t addr, unsigned int len, unsigned int target_crc)
{
    bb_set_hot_upgrade_crc32_in_t  in  = {};
    bb_set_hot_upgrade_crc32_out_t out = {};

    in.seq   = 3673;
    in.addr  = (unsigned int)addr;
    in.len   = len;
    in.crc32 = target_crc;
    int ret  = bb_ioctl(pdev, BB_SET_HOT_UPGRADE_CRC32, &in, &out);
    if (ret || out.ret != 0) {
        printf("BB_SET_HOT_UPGRADE_CRC32 err!\n");
        return -1;
    }

    return 0;
}

int ar8030_upgrade_partition(bb_dev_handle_t* pdev, uint32_t addr, unsigned int len, unsigned char* data)
{
    unsigned int o_crc  = 0;
    unsigned int i      = 0;
    unsigned int offset = 0;
    int          ret    = 0;

    // cal crc
    o_crc = CRC::Calculate(data, len, CRC::CRC_32());

    printf("%s %d addr %#" PRIx64 " len %d crc %08x\n", __func__, __LINE__, (uint64_t)addr, len, o_crc);

    // split
    for (i = 0; i < (len / max_size); ++i) {
        // write 768
        printf("update process %3.2f%%\r", (float)offset * 100 / len);
        offset = i * max_size;
        ret    = ar8030_upgrade_write(pdev, addr + offset, max_size, data + offset);
        if (ret < 0) {
            printf("ar8030_upgrade_write failed!\n");
            return -1;
        }
    }

    if (len % max_size) {
        printf("update process %3.2f%%\r", (float)offset * 100 / len);
        offset = i * max_size;
        ret    = ar8030_upgrade_write(pdev, addr + offset, len % max_size, data + offset);
        if (ret < 0) {
            printf("ar8030_upgrade_write failed!\n");
            return -1;
        }
    }
    printf("update process 100%%  \n");

#if 1
    // get crc
    ret = ar8030_upgrade_chk_crc(pdev, addr, len, o_crc);
    if (ret < 0) {
        printf("ar8030_upgrade_chk_crc failed!\n");
        return -1;
    }
#endif
    return 0;
}

int ar8030_upgrade_partition_byname(bb_dev_handle_t*     pdev,
                                    struct upgrade_hdr*  hdr,
                                    struct part_info*    partitions,
                                    struct segment_info* segments,
                                    unsigned char*       data,
                                    const char*          partname)
{
    int                i, ret = 0;
    int                segment_idx    = -1;
    unsigned long long partition_addr = 0;
    unsigned long long partition_len  = 0;

    for (i = 0; i < hdr->partitions; i++) {
        if (partitions[i].is_upgrade) {
            // printf("part %d, name %8s, length 0x%08llx, upgrade %d seg %d\n", i,
            //     partitions[i].name, partitions[i].length, partitions[i].is_upgrade, segment_idx);
            ++segment_idx;
        }

        if (0 == strcmp((char*)partitions[i].name, partname)) {
            break;
        }
    }

    // no not upgrade but return 0
    if (!partitions[i].is_upgrade)
        return 0;

    if (-1 == segment_idx || partitions[i].flash_offset != segments[segment_idx].flash_offset) {
        printf("partition %s info is not match! 0x%x 0x%x %d\n",
               partitions[i].name,
               (int)partitions[i].flash_offset,
               (int)segments[segment_idx].flash_offset,
               (int)segment_idx);
        return -1;
    }

    partition_addr = segments[segment_idx].flash_offset + GPT_FLASH_OFFSET;
    partition_len  = segments[segment_idx].size_decompress;
    data += segments[segment_idx].img_offset;
    ret = ar8030_upgrade_partition(pdev, partition_addr, partition_len, data);
    if (ret < 0) {
        printf("upgrade partition %s failed!\n", partname);
    }
    return ret;
}

int main(int argc, char** argv)
{
    int         port      = BB_PORT_DEFAULT;
    const char* file_path = "artosyn-upgrade-ar8030.img";
    const char* ip        = "127.0.0.1";
    while (1) {
        int                  option_index   = 0;
        int                  c              = 0;
        static struct option long_options[] = {
            {"port",  required_argument, 0, 'p'},
            { "ip",   required_argument, 0, 'i'},
            { "file", required_argument, 0, 'f'},
            { 0,      0,                 0, 0  },
        };

        c = getopt_long(argc, argv, "i:p:f:", long_options, &option_index);
        if (c == -1) {
            break;
        }

        switch (c) {
        case 'i':
            ip = optarg;
            break;
        case 'p':
            port = (int)strtoul(optarg, NULL, 10);
            break;
        case 'f':
            file_path = optarg;
            break;
        default:
            break;
        }
    }

    bb_host_t* phost;

    int ret = bb_host_connect(&phost, ip, port);

    if (ret) {
        printf("connect failed = %d\n", ret);
        exit(-1);
    }

    bb_dev_t** devs;

    int sz = bb_dev_getlist(phost, &devs);
    if (sz <= 0) {
        printf("dev cnt = 0\n");
        exit(-1);
    }

    bb_dev_handle_t* pdev = bb_dev_open(devs[0]);

    if (!pdev) {
        printf("can't open dev!!\n");
        exit(-1);
    }
    img8030 img(file_path);

    img.dump_upgrade_file();

    if (img.hdr->rom_size) {
        printf("update romcode datap = %p , len = %x\n", img.romcode, img.hdr->rom_size);
        ar8030_upgrade_partition(pdev, 0, img.hdr->rom_size, img.romcode);
    }

    // write gpt table 1
    uint32_t partition_addr = 0x0 + img.hdr->rom_size + GPT_FLASH_SIZE;
    ret                     = ar8030_upgrade_partition(pdev, partition_addr, GPT_FLASH_SIZE, img.gpt);
    if (ret < 0)
        return -1;
    // write partitions app1
    ret = ar8030_upgrade_partition_byname(pdev, img.hdr, img.partitions, img.segments, (unsigned char*)img.img, "app1");
    if (ret < 0)
        return -1;

    // write gpt table 0
    partition_addr = 0x0 + img.hdr->rom_size;
    ret            = ar8030_upgrade_partition(pdev, partition_addr, GPT_FLASH_SIZE, img.gpt);
    if (ret < 0)
        return -1;

    // write partitions app0
    ret = ar8030_upgrade_partition_byname(pdev, img.hdr, img.partitions, img.segments, (unsigned char*)img.img, "app0");
    if (ret < 0)
        return -1;

    return 0;
}